思考: DefinitelyTypedで、http.createServer()とhttp2.createServer()のハンドラーの型が合わなくて考えていること
#TypeScript #DefinitelyTyped #Node.js #HTTP/2 #思考
(思考がついているのは、解決したわけではないから)
まず、Node.jsのhttp2にはCompatibility APIが用意されている。
これのおかげで、JavaScriptでNode.jsを使うときには、同じハンドラーをhttp2.createServer()に渡せば、HTTP/2対応できる互換性がある。ただ、ダックタイピングで実現している(全JavaScriptそうかもだけど)ので、執筆時のTypeScriptの型定義だと、ハンドラーの型が合わなくて、コンパイルエラーする。
執筆時のDefinitelyTypedだと、http.createServer(handler)とhttp2.createServer(handler)の型定義で使われているhandlerに互換性がない。
httpだと、handlerの型が、(req: IncomingMessage, res: ServerResponse) => voidで、
http2だと、handlerの型が、(request: Http2ServerRequest, response: Http2ServerResponse) => void
になっていて、
IncomingMessage !== Http2ServerRequest だし、
ServerResponse !== Http2ServerResponseなため、
同じhandlerを使って、HTTP/1用とHTTP/2用のサーバーを立てることができない。(anyでキャストすればできるんだろうけど、それは最終手段にしたい。はやり型システムの恩恵を受けたい)
解決案としては、
IncomingMessage と Http2ServerRequestに共通のインターフェースもしくはクラスをもたせる。
ServerResponseとHttp2ServerResponseも同様に、共通のインターフェースもしくはクラスをもたせる。
が良い気がする。
色々と変更方法を考えていると、Http2ServerResponseがstream.Writableを継承してないことが引っかかるようになった。Http2ServerResponse#write()はできるが、stream.Writableを継承しているわけではなく、独自にwrite()メソッドを生やしている。
()
そして、Http2ServerResponse#write()とWritable#write()の引数の型も違う。
目指すところ的には、
出来るだけ型定義の変更を少なくかつ、
一番の理想は、DefinitelyTypedいじらず、interfaceを追加してそれを使うようにしたら、ちゃんと動くみたいなもの。
追加するインターフェースまたはクラスの一般性を上げたい
== 共通項はなるべくくくり出したい
同じ定義を何度も繰り返すのは避けたい
型定義上ではHttp2ServerResponseをのWritableを継承させてしまうと楽だが、Writableには_write()メソッドなど、Http2ServerResponseにないものも生えていて、これがコーディング中に実装はないのに補完されるようになるのも困る。
TypeScriptの型システムがとても柔軟かつ強力であるが、DefinitelyTypedはJavaScriptに後付けで型付けしているため、実装と型定義が結びついてない問題にどうしてもぶつかる。これはTypeScriptが悪いわけでもJavaScriptが悪いわけでもなく、JavaScript動的型付けの文化に、頑張って静的型付けを入り込もうとするのが難しいねって話だと思う。恐らくこれぐらいの問題の解決は十分いままでDefinitelyTypedでも解決されてきてそうなことだし、時間をおいて考えれば良い方法がみつかるかもしれない。